using System;
using System.ComponentModel;
using System.IO;
using DslCrossModelFramework.NameResolution;
using Microsoft.VisualStudio.Modeling;

namespace DslCrossModelFramework.ModelTraversal {

    /// <summary>
    /// Represents a single element in a <see cref="ModelPath"/>
    /// </summary>
    public interface IModelPathSegment {

        /// <summary>
        /// Gets the actual model element. 
        /// This will be the proxy element if the model element is defined as <see cref="ICrossModelProxyElement"/>
        /// </summary>
        /// <value>The actual model element.</value>
        ModelElement ActualModelElement{ get;} 

        /// <summary>
        /// Gets the current model element.
        /// This will be the proxied (target) model element if the actual model element is defined as <see cref="ICrossModelProxyElement"/>
        /// </summary>
        /// <value>The model element.</value>
        ModelElement ModelElement { get;}

        /// <summary>
        /// Gets the model file info.
        /// </summary>
        /// <value>The model file info.</value>
        IModelFileInfo ModelFileInfo{ get; }

        /// <summary>
        /// Gets the serialized string.
        /// </summary>
        /// <returns></returns>
        string GetSerializedString();

        /// <summary>
        /// Gets the string representation of the model element using the <see cref="NameResolutionManager"/>
        /// </summary>
        /// <param name="scope">The scope.</param>
        /// <returns></returns>
        string ToString(string scope);

    }

    /// <summary>
    /// Represents a single link element in a <see cref="ModelPath"/>
    /// </summary>
    [Browsable(false)]
    public interface IModelLinkPathSegment : IModelPathSegment {
        /// <summary>
        /// Gets the direction that the model traversal takes over the link element.
        /// </summary>
        /// <value>The direction that the model traversal takes over the link element.</value>
        ModelPathLinkDirection Direction{ get; }
    }

    public abstract class ModelPathSegmentBase : IModelPathSegment {

        public ModelPathSegmentBase(string serializedString, CrossModelManager manager) {
            this.manager = manager;
            this.serializedString = serializedString;
        }

        public ModelPathSegmentBase(ModelElement modelElement, CrossModelManager manager) {
            this.modelElement = modelElement;
            this.isDeserialized = true;
            this.manager = manager;


            modelFileInfo = manager.GetModelDirectory().GetModelFileInfo(modelElement);

            modelFileInfo.Unloading += new EventHandler<ModelFileEventArgs>(modelFileInfo_Unloading);
            if(modelFileInfo == null) {
                //TODO: Review Exception
                
                throw new ApplicationException(string.Format("Cannot find model file for modelElement {0}", manager.GetModelElementName( modelElement)));
            }

#pragma warning disable DoNotCallOverridableMethodsInConstructor
            this.serializedString = GetSerializedString();
#pragma warning restore DoNotCallOverridableMethodsInConstructor
        }

        void modelFileInfo_Unloading(object sender, ModelFileEventArgs e) {
            this.isDeserialized = false;
            this.modelElement = null;
        }

        protected CrossModelManager manager;

        public bool IsDeserialized {
            get { return isDeserialized; }
        }
        protected bool isDeserialized;

        protected string serializedString;


        /// <summary>
        /// Gets or sets the actual model element.
        /// This will be the proxy element if the model element is defined as <see cref="ICrossModelProxyElement"/>
        /// </summary>
        /// <value>The actual model element.</value>
        public ModelElement ActualModelElement {
            get {
                if(!isDeserialized) {
                    DeserializeFromString();
                }
                return modelElement;
            }

        }

        /// <summary>
        /// Gets the current model element.
        /// This will be the proxied (target) model element if the actual model element is defined as <see cref="ICrossModelProxyElement"/>
        /// </summary>
        /// <value>The model element.</value>
        public ModelElement ModelElement {
            get {
                if (!isDeserialized || modelElement == null || modelElement.Store == null) {
                    DeserializeFromString();
                }

                if (!(modelElement is ICrossModelProxyElement)) {
                    return modelElement;
                }
                else {
                    return ((ICrossModelProxyElement)modelElement).TargetPath == null
                        ? null
                        : ((ICrossModelProxyElement)modelElement).TargetPath.TargetModelElement;
                }
            }
        }

        /// <summary>
        /// Gets the model file info.
        /// </summary>
        /// <value>The model file info.</value>
        public IModelFileInfo ModelFileInfo {
            get {
                if (!isDeserialized) {
                    DeserializeFromString();
                }
                return modelFileInfo;
            }
        }

        protected ModelElement modelElement;
        protected IModelFileInfo modelFileInfo;

        public abstract void DeserializeFromString();

        public virtual string GetSerializedString() {
            if (!isDeserialized) {
                return serializedString;
            }
            return string.Format("[Element|{0}|{1}]", this.ModelFileInfo.ModelFile.UniqueModelFileReference, manager.GetModelElementReference(this.modelElement));
        }


        public override string ToString() {
            return NameResolutionManager.Current.GetElementName(ModelElement);
        }

        public virtual string ToString(string scope) {
            return NameResolutionManager.Current.GetElementName(ModelElement, scope);
        }

    }

    /// <summary>
    /// Represents a single element in a <see cref="ModelPath"/>
    /// </summary>
    public class ModelElementPathSegment : ModelPathSegmentBase {
        public ModelElementPathSegment(ModelElement modelElement, CrossModelManager manager) : base(modelElement, manager) { }

        public ModelElementPathSegment(string serializedString, CrossModelManager manager) : base(serializedString, manager) {}


        public override void DeserializeFromString() {
            try {
                string[] vars = serializedString.TrimStart('[').TrimEnd(']').Split('|');
                if (vars[0] != "Element") {
                    throw new ModelPathDeserializationException("Expected 'Element' in serialized segment string");
                }

                string uniqueModeFileReference = vars[1];
                foreach (IModelFileInfo currentModeFileInfo in manager.GetModelDirectory().ModelFiles) {
                    if (currentModeFileInfo.ModelFile.Equals(uniqueModeFileReference)) {
                        this.modelFileInfo = currentModeFileInfo;
                    }
                }
                if (this.modelFileInfo == null) {
                    throw new FileNotFoundException(string.Format("Could not find the model file for the reference {0}", uniqueModeFileReference));
                }

                string modelElementRef = vars[2];
                this.modelFileInfo.Load();
                this.modelElement = manager.ResolveModelElementReference(modelElementRef, this.modelFileInfo.ModelFile);

                isDeserialized = true;
            }
            catch (Exception ex) {
                throw new ModelPathDeserializationException(string.Format("Error Deserializing Segment '{0}'", ex.Message), ex);
            }
        }
    }


    public class ModelElementLinkPathSegment : ModelPathSegmentBase, IModelLinkPathSegment {

        public ModelElementLinkPathSegment(string serializedString, CrossModelManager manager) : base(serializedString, manager) {}

        public ModelElementLinkPathSegment(ElementLink link, ModelPathLinkDirection direction, CrossModelManager manager ) : base(link, manager) {
            this.direction = direction;
        }

        public ModelPathLinkDirection Direction {
            get { return direction; }
        }
        private ModelPathLinkDirection direction;

        public override string GetSerializedString() {
            if(!isDeserialized) {
                return serializedString;
            }
            return string.Format("[Link|{0}|{1}|{2}]", this.ModelFileInfo.ModelFile.UniqueModelFileReference, manager.GetModelElementReference(this.ActualModelElement), ConvertDirectionToString());
        }

        private string ConvertDirectionToString() {
            switch(this.direction) {
                case ModelPathLinkDirection.SourceToTarget:
                    return "+";
                case ModelPathLinkDirection.TargetToSource:
                    return "-";
                default:
                    return "";
            }
        }

        public override void DeserializeFromString() {
            try {
                string[] vars = serializedString.Split('|');
                if (vars[0] != "Link") {
                    throw new ModelPathDeserializationException("Expected 'Link' in serialized segment string");
                }

                string uniqueModeFileReference = vars[1];
                foreach (IModelFileInfo currentModeFileInfo in manager.GetModelDirectory().ModelFiles) {
                    if (currentModeFileInfo.ModelFile.Equals(uniqueModeFileReference)) {
                        this.modelFileInfo = currentModeFileInfo;
                    }
                }
                if (this.modelFileInfo == null) {
                    throw new FileNotFoundException(string.Format("Could not find the model file for the reference {0}", uniqueModeFileReference));
                }

                Guid modelElementGuid = new Guid(vars[2]);

                string modelElementRef = vars[2];
                this.modelFileInfo.Load();
                this.modelElement = manager.ResolveModelElementReference(modelElementRef, this.modelFileInfo.ModelFile);

                this.direction = vars[3] == "+"
                                     ? ModelPathLinkDirection.SourceToTarget
                                     : vars[3] == "-"
                                           ? ModelPathLinkDirection.TargetToSource
                                           : ModelPathLinkDirection.Unknown;

                isDeserialized = true;
            }
            catch (Exception ex) {
                throw new ModelPathDeserializationException(string.Format("Error Deserializing Segment '{0}'", ex.Message), ex);
            }
        }
    }

    //public class ElementLinkPathSegment<TModelLink> : IModelLinkPathSegment where TModelLink : ElementLink {

    //    public ElementLinkPathSegment(TModelLink link, ModelPathLinkDirection direction) {
    //        this.direction = direction;
    //        this.modelElement = link;
    //    }


    //    public ModelPathLinkDirection Direction {
    //        get { return direction; }
    //    }

    //    private ModelPathLinkDirection direction;


    //    public ModelElement ModelElement {
    //        get { return modelElement; }
    //        set { modelElement = value; }
    //    }
    //    private ModelElement modelElement;

    //}

    //public abstract class ModelElementPathSegment<TModelElement> : IModelPathSegment where TModelElement : ModelElement {


    //    public ModelElement ModelElement {
    //        get { return modelElement; }
    //        set { modelElement = value; }
    //    }
    //    private ModelElement modelElement;

    //}


}